package org.senseframework.support.lucene.core;


import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.cjk.CJKAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TrackingIndexWriter;
import org.apache.lucene.search.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.senseframework.support.lucene.IndexCase;
import org.senseframework.support.lucene.annotation.IndexDomain;
import org.senseframework.support.lucene.annotation.IndexFiled;
import org.senseframework.support.lucene.exception.LuceneException;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

/**
 * Created by lanjian on 2015/1/14.
 */
public class DocumentDefinition {

    private static Log log= LogFactory.getLog(DocumentDefinition.class);


    private SearcherManager mgr;

    private TrackingIndexWriter tkWriter;
    private ControlledRealTimeReopenThread<IndexSearcher> crtThread;

    private static String base_path="lucene.index.path";


    public void release(IndexSearcher searcher){
        try {
            mgr.release(searcher);
        } catch (IOException e) {
            log.error("release",e);
        }
    }


    public void index(DataRunner dataRunner){
        dataRunner.handler(this);
    }

    public QueryResult search(SearcherHandler searcherHandler){
        IndexSearcher searcher=null;
        try {
            //更新看看内存中索引是否有变化如果，有一个更新了，其他线程也会更新
            mgr.maybeRefresh();
            //利用acquire 方法获取search，执行此方法前须执行maybeRefresh
            searcher = mgr.acquire();
            return searcherHandler.handler(searcher);
        } catch (Exception e) {
            log.error("search", e);
        }finally{
            try {
                //释放searcher，
                mgr.release(searcher);
            } catch (Exception e) {
                log.error("search", e);
            }
        }
        return null;
    }


    public void deleteAll(){
        try {
            tkWriter.deleteAll();
        } catch (IOException e) {
            log.error("deleteAll:",e);
        }
    }


    public void deleteDocument(Term term){
        try {
            tkWriter.deleteDocuments(term);
        } catch (IOException e) {
            log.error("write:",e);
        }
    }
    public void deleteDocument(Query query){
        try {
            tkWriter.deleteDocuments(query);
        } catch (IOException e) {
            log.error("write:",e);
        }
    }

    public void addDocument(Object obj){
        try {
            tkWriter.addDocument(objToDocument(obj));
        } catch (IOException e) {
            log.error("write:",e);
        }
    }


    public static class FiledDefinition {
        private String filedName;
        private Index.Field filedType;
        private Class type;
        private IndexCase indexCase;
        private float boost;
        private Index.Store store;

        public FiledDefinition(String filedName, Index.Field filedType,Class type,IndexCase indexCase,Index.Store store) {
            this.filedName = filedName;
            this.filedType = filedType;
            this.type=type;
            this.indexCase=indexCase;
            this.store=store;
        }


        public String getFiledName() {
            return filedName;
        }

        public float getBoost() {
            return boost;
        }

        public void setBoost(float boost) {
            this.boost = boost;
        }

        public void setFiledName(String filedName) {
            this.filedName = filedName;
        }

        public Index.Field getFiledType() {
            return filedType;
        }

        public void setFiledType(Index.Field filedType) {
            this.filedType = filedType;
        }

        public Object stringValue(Class ref,Document document){
            String value = document.get(filedName);
            if(ref.isEnum()){
                Object[] ts=ref.getEnumConstants();
                for(Object obj:ts){
                    if(value.equals(obj.toString())){
                        return obj;
                    }
                }
                return null;
            }else{
                return value;
            }
        }
        public Object realValue(Document document) {
            if(filedType.equals(Index.Field.String) ){
                return stringValue(type,document);
            }else if(filedType.equals(Index.Field.Int)){
                String value = document.get(filedName);
                if(value==null){
                    return null;
                }
                return Integer.valueOf(value);
            }else if(filedType.equals(Index.Field.Long)){
                String value = document.get(filedName);
                if(value==null){
                    return null;
                }
                return Long.valueOf(value);
            }else if(filedType.equals(Index.Field.Double)){
                String value = document.get(filedName);
                if(value==null){
                    return null;
                }
                return Double.valueOf(value);
            }else if(filedType.equals(Index.Field.Float)){
                String value = document.get(filedName);
                if(value==null){
                    return null;
                }
                return Float.valueOf(value);
            }else if(filedType.equals(Index.Field.Array)){
                String[] value = document.getValues(filedName);
                return  new HashSet<String>(Arrays.asList(value));
            }else if(filedType.equals(Index.Field.Boolen)){
                String value = document.get(filedName);
                if(value==null){
                    return false;
                }
                return Boolean.valueOf(value);
            }else{
                throw new LuceneException("not type found");
            }

        }

        public String caseString(String str){
               if(IndexCase.lower.equals(indexCase)){
                   return str.toLowerCase();
               }else if(IndexCase.upper.equals(indexCase)){
                   return str.toUpperCase();
               }else{
                   return str;
               }
        }

        public void assembleValue(Document document,String ...value) {
            if(filedType.equals(Index.Field.String)){
                    SearchField searchField=new SearchField(filedName,caseString(value[0]));
                    searchField.setBoost(boost);
                    document.add(searchField);
            }if(filedType.equals(Index.Field.Boolen) ){
                document.add(new SearchField(filedName, value[0]));
            }else if(filedType.equals(Index.Field.Int)){
                document.add(new IntField(filedName, Integer.valueOf(value[0]),store.store()));
            }else if(filedType.equals(Index.Field.Long)){
                document.add(new LongField(filedName, Long.valueOf(value[0]),store.store()));
            }else if(filedType.equals(Index.Field.Double)){
                document.add(new DoubleField(filedName, Double.valueOf(value[0]),store.store()));
            }else if(filedType.equals(Index.Field.Float)){
                document.add(new FloatField(filedName, Float.valueOf(value[0]),store.store()));
            }else if(filedType.equals(Index.Field.Array)){
                for(String str:value){
                        SearchField searchField=new SearchField(filedName,caseString(str));
                        searchField.setBoost(boost);
                        document.add(searchField);
                }
            }
        }
    }



    private Class clazz;
    private String domain;
    private List<FiledDefinition> filedDefinitions;

    public void addFiledDefinition(FiledDefinition filedDefinition) {
        if (filedDefinitions == null) {
            filedDefinitions = new ArrayList<FiledDefinition>();
        }
        filedDefinitions.add(filedDefinition);
    }

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public List<FiledDefinition> getFiledDefinitions() {
        return filedDefinitions;
    }

    public void setFiledDefinitions(List<FiledDefinition> filedDefinitions) {
        this.filedDefinitions = filedDefinitions;
    }

    public DocumentDefinition(String classStr) {
        try {
            Class clazz = Class.forName(classStr);
            init(clazz);
        } catch (ClassNotFoundException e) {
            throw new LuceneException(e);
        }
    }

    public DocumentDefinition(Class clazz) {
        init(clazz);
    }

    public void init(Class clazz){
        try {
            String path= "/opt/index/"+clazz.getSimpleName().toLowerCase();
            Directory fsDir = FSDirectory.open(new File(path));
            //创建writer
            IndexWriterConfig config=new IndexWriterConfig(Version.LATEST,new CJKAnalyzer());
//            config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
            IndexWriter writer = new IndexWriter(fsDir,config);
            //新建SearcherManager
            //true 表示在内存中删除，false可能删可能不删，设为false性能会更好一些
            mgr = new SearcherManager(writer,true,new SearcherFactory());
            //ControlledRealTimeReopenThread 构造是必须有的，主要将writer装，每个方法都没有commit 操作。
            tkWriter = new TrackingIndexWriter(writer);//为writer 包装了一层
            //创建线程，线程安全的，我们不须处理
            crtThread = new ControlledRealTimeReopenThread<IndexSearcher>(tkWriter, mgr, 5.0, 0.025);
            crtThread.setDaemon(true);//设为后台进程
            crtThread.setName(clazz.getSimpleName() + "index");
            crtThread.start();//启动线程
        }catch (Exception ex){
            log.error("init",ex);
        }

        if (clazz.isAnnotationPresent(IndexDomain.class)) {
            IndexDomain indexDomain = (IndexDomain) clazz.getAnnotation(IndexDomain.class);
            setClazz(clazz);
            setDomain(indexDomain.domain());
            field2DocumentDefinition(clazz.getDeclaredFields(),this);
            if(clazz.getSuperclass()!=null){
                field2DocumentDefinition(clazz.getSuperclass().getDeclaredFields(),this);
            }
        } else {
            throw new LuceneException("this class " + clazz.getSimpleName() + " not @IndexDomain");
        }
    }



    private void field2DocumentDefinition(Field[] fields,DocumentDefinition documentDefinition){
        for (Field field : fields) {
            if (field.isAnnotationPresent(IndexFiled.class)) {
                IndexFiled indexFiled = field.getAnnotation(IndexFiled.class);
                documentDefinition.addFiledDefinition(new FiledDefinition(field.getName(), indexFiled.field(),field.getType(),indexFiled.indexCase(),indexFiled.store()));
            }
        }
    }


    protected  <T> T documentToObj(Document document) {
        try {
            Object obj = getClazz().newInstance();
            for (FiledDefinition filedDefinition : filedDefinitions) {
                PropertyUtils.setProperty(obj, filedDefinition.filedName, filedDefinition.realValue(document));
            }
            return (T) obj;
        } catch (Exception e) {
            throw new LuceneException(e);
        }
    }

    protected Document objToDocument(Object obj) {
        try {
            Document document=new Document();
            for (FiledDefinition filedDefinition : filedDefinitions) {
                if(filedDefinition.getFiledType().equals(Index.Field.Array)){
                    String[] value= BeanUtils.getArrayProperty(obj, filedDefinition.filedName);
                    if(value!=null){
                        filedDefinition.assembleValue(document,value);
                    }
                }else{
                    String value= BeanUtils.getProperty(obj, filedDefinition.filedName);
                    if(value!=null){
                        filedDefinition.assembleValue(document,value);
                    }
                }
            }
            if(obj instanceof  DocumentBoostHandler){
                DocumentBoostHandler documentBoostHandler= (DocumentBoostHandler) obj;
                SearchField searchField=new SearchField("boost", documentBoostHandler.boost());
                searchField.setBoost(documentBoostHandler.boost());
                document.add(searchField);
            }
            return document;
        }catch (Exception ex){
            throw new LuceneException(ex);
        }

    }
}
